#ifndef _IP2NAME_H__
#define _IP2NAME_H__

#include <sqlite3.h>
#ifdef linux
#include <arpa/inet.h>
#endif
#include "uint32pool.h"
#include "list.h"
#include "log.h"


#define IP2NAME_HOST_MAX_LEN 1024
struct tcp_dns_content {
    uint8_t *cache;
    int cache_len;
    uint32_t is_not_tcp_dns;
};

struct ip2name {
    struct uint32pool *ip2name_pool;//   = NULL;
    struct uint32pool *ip2name_pool_old;
    uint32_t ip2name_pool_size;// = 0;
    sqlite3 *ip2name_db;// = NULL;
    int ip2name_timeout;
    time_t ip2name_timeout_last;// = 0;
    uint32_t ip2name_clear_count;// = 0;
    int load_ip2name_from_sqlite_count;// = 0;
};

struct ip2name_dns_head {
    uint16_t trans_id;
    uint16_t flags;
    uint16_t questions;
    uint16_t answer_rrs;
    uint16_t authority_rrs;
    uint16_t additional_rrs;
} __attribute__((packed));

static inline int check_hostname(char *dns, int len)
{
    char c;
    while (len > 0) {
        len--;
        c = dns[len];
        if (c == '.' || c == '-') continue;
        if ('a' <= c && c <= 'z') continue;
        if ('0' <= c && c <= '9') continue;
        if ('A' <= c && c <= 'Z') continue;
        return -1;
    }
    return 0;
}

static inline void tcp_dns_context_fini(struct tcp_dns_content *content)
{
    if (content) {
        if (content->cache) {
            free(content->cache);
            content->cache = NULL;
        }
    }
}

static inline int litesql3_callback(void *NotUsed, int argc, char **argv, char **azColName)
{
    int i;
    for(i=0; i<argc; i++){
        LOGERR("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL");
    }
    LOGERR("\n");
    return 0;
}

//static inline void ip2name_add(struct ip2name *ip2name, uint32_t ipv4_l, char *hostname, int hostname_len, int add_to_sqlite, int verbose)
#define ip2name_add(ip2name, ipv4_l, hostname, hostname_len, add_to_sqlite, verbose) do { if (ip2name) { \
    char _ipstr[60]; uint32_t _ipv4_l = ipv4_l; uint32_t _ipv4 = be32toh(_ipv4_l); const char *_hostname = hostname; int _hostname_len = hostname_len; \
    struct uint32pool_userdata *userdata = uint32pool_find(ip2name->ip2name_pool, _ipv4_l, NULL, NULL); \
    if (userdata) { \
        if (strlen(userdata->data) != _hostname_len || memcmp(userdata->data, _hostname, _hostname_len)) { \
            if (verbose) { \
                LOGMSG("update ip2name_pool %-15s %36s -> %-36.*s\n", inet_ntop(AF_INET, &_ipv4, _ipstr, sizeof(_ipstr)), (const char *)userdata->data, _hostname_len, _hostname); \
            } \
            char *new = realloc(userdata->data, _hostname_len + 1); \
            if (new) { \
                userdata->data = new; \
                memcpy(userdata->data, _hostname, _hostname_len); \
                memcpy(userdata->data + _hostname_len, "", 1); \
            } else { \
                if (verbose) { \
                    LOGMSG("update ip2name_pool %-15s %36s -> %-36.*s fail[%m]\n", inet_ntop(AF_INET, &_ipv4, _ipstr, sizeof(_ipstr)), (const char *)userdata->data, _hostname_len, _hostname); \
                } \
            } \
        } \
    } else { \
        char *data = malloc(_hostname_len + 1); \
        if (data) { \
            memcpy(data, _hostname, _hostname_len); \
            memcpy(data + _hostname_len, "", 1); \
            if (uint32pool_add(ip2name->ip2name_pool, _ipv4_l, _ipv4_l, data, 1)) { \
                if (verbose) { \
                    LOGMSG("add to ip2name_pool %-15s %36.*s fail\n", inet_ntop(AF_INET, &_ipv4, _ipstr, sizeof(_ipstr)), _hostname_len, _hostname); \
                } \
                free(data); \
            } else { \
                struct uint32pool_userdata *findout_userdata = uint32pool_find(ip2name->ip2name_pool, _ipv4_l, NULL, NULL); \
                if (verbose) { \
                    if (findout_userdata) { LOGMSG("add to ip2name_pool %-15s %36.*s ok\n", inet_ntop(AF_INET, &_ipv4, _ipstr, sizeof(_ipstr)), _hostname_len, _hostname); \
                    } else {                LOGMSG("add to ip2name_pool %-15s %36.*s fail\n", inet_ntop(AF_INET, &_ipv4, _ipstr, sizeof(_ipstr)), _hostname_len, _hostname); \
                    } \
                } \
                ip2name->ip2name_pool_size++; \
                if (add_to_sqlite) { \
                    char sql[1024]; sql[sizeof(sql)-1] = 0; \
                    snprintf(sql, sizeof(sql)-1, "insert into ip2name values(%u, \"%.*s\")", _ipv4_l, _hostname_len, _hostname); \
                    LOGMSG("sql %s\n", sql); \
                    if (ip2name->ip2name_db) { \
                        char *zErrMsg = 0; \
                        int ret = sqlite3_exec(ip2name->ip2name_db, sql, litesql3_callback, 0, &zErrMsg); \
                        if(ret != SQLITE_OK) { LOGFEW("SQL error: %s\n", zErrMsg); sqlite3_free(zErrMsg); } \
                    } \
                } \
            } \
        } else { \
            LOGFEW("add to ip2name_pool %-15s %36.*s fail\n", inet_ntop(AF_INET, &_ipv4, _ipstr, sizeof(_ipstr)), _hostname_len, _hostname); \
        } \
    } \
} } while(0)

static inline int sqlite_load_ip2name_callback(void *NotUsed, int argc, char **argv, char **azColName)
{
    struct ip2name *ip2name = NotUsed;
    int i;
    if (argc != 2) {
        LOGFEW("error, field count is not 2\n");
        for(i=0; i<argc; i++){ LOGERR("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL"); }
    } else {
        if (ip2name) {
            //for(i=0; i<argc; i++){ LOGERR("%s = %s\n", azColName[i], argv[i] ? argv[i] : "NULL"); }
            uint32_t ipv4_l = strtoull(argv[0], NULL, 10);
            ip2name_add(ip2name, ipv4_l, argv[1], strlen(argv[1]), 0, 0);
            ip2name->load_ip2name_from_sqlite_count++;
        }
    }
    return 0;
}

#define ip2name_create(ip2name, db_path, timeout) ({ \
        const char *_db_path = db_path; \
    memset((ip2name), 0, sizeof(*(ip2name))); \
    (ip2name)->ip2name_pool = uint32pool_new(); \
    (ip2name)->ip2name_timeout = timeout; \
    if (_db_path && _db_path[0]) { \
        int ret = sqlite3_open(_db_path, &(ip2name)->ip2name_db); \
        if (ret) { \
            LOGERR("Can't open database: %s [%s]\n", db_path, sqlite3_errmsg((ip2name)->ip2name_db)); \
            sqlite3_close((ip2name)->ip2name_db); \
            (ip2name)->ip2name_db = NULL; \
        } \
    } \
    if ((ip2name)->ip2name_db) { \
        char *zErrMsg = 0; \
        ret = sqlite3_exec((ip2name)->ip2name_db, "create table ip2name(ip bigint primary key, domain varchar(64))", litesql3_callback, 0, &zErrMsg); \
        if (ret != SQLITE_OK){ \
            LOGERR("SQL error: %s\n", zErrMsg); \
            sqlite3_free(zErrMsg); \
        } \
        ret = sqlite3_exec((ip2name)->ip2name_db, "select * from ip2name", sqlite_load_ip2name_callback, ip2name, &zErrMsg); \
        if (ret != SQLITE_OK) { \
            LOGERR("load from sqlite error [%s]\n", zErrMsg); \
            sqlite3_free(zErrMsg); \
        } \
        LOGERR("load_ip2name_from_sqlite_count %u\n", (ip2name)->load_ip2name_from_sqlite_count); \
    } \
    0; })

static inline void ip2name_check_timeout(struct ip2name *ip2name)
{
    if (ip2name->ip2name_timeout > 0) {
        if (ip2name->ip2name_timeout_last == 0) ip2name->ip2name_timeout_last = time(NULL);
        uint32_t interval = time(NULL) - ip2name->ip2name_timeout_last;
        if (interval > ip2name->ip2name_timeout) {
            ip2name->ip2name_timeout_last = time(NULL);
            if (ip2name->ip2name_pool_old) uint32pool_destroy(ip2name->ip2name_pool_old, NULL);
            ip2name->ip2name_pool_old = ip2name->ip2name_pool;
            ip2name->ip2name_pool= uint32pool_new();
            ip2name->ip2name_pool_size = 0;
            ip2name->ip2name_clear_count++;
        }
    }
}

static inline int ip2name_dns_data_to_host(struct ip2name *ip2name, const void *__app_data, int app_data_len, char *hostname, int *hostname_buf_len, int *is_aaaa, int verbose, void (*callback)(char *hostname, int hostname_len, uint32_t ip_l, void *c), void *c, const char *caller_func, int caller_line)
{
    if (ip2name == NULL) return -1;
    const uint8_t *app_data = (const uint8_t *)__app_data;
    const uint8_t *end = app_data + app_data_len;
    const uint8_t *rest_data = app_data + sizeof(struct ip2name_dns_head);
    int hostname_len = 0;
    int part_count = 0;
    while (rest_data < end) {
        uint8_t n = *rest_data;
        rest_data++;
        if (n == 0) break;
        if (rest_data + n > end) {
            LOGMSG("parse out [%.*s], and next part is wrong\n", hostname_len, hostname);
            return -1;
        }
        if (hostname_len + 1 + n > *hostname_buf_len) { LOGMSG("parsed out %ubyte, and next part is too long %ubyte [caller at %s %u]\n", hostname_len, n, caller_func, caller_line); return -1; }
        memcpy(hostname + hostname_len, rest_data, n);
        rest_data += n;
        hostname_len += n + 1;
        hostname[hostname_len-1] = '.';
        part_count++;
    }
    if (hostname_len <= 1 || part_count <= 1) {
        LOGMSG("parse out [%.*s], len too short or part count to short\n", hostname_len, hostname);
        return -1;
    }
    hostname_len--;
    LOGMSG("%d byte [%.*s]\n", hostname_len, hostname_len, hostname);
    if (check_hostname(hostname, hostname_len)) {
        LOGMSG("hostname format error[%.*s]\n", hostname_len, hostname);
        return -1;
    }
    *hostname_buf_len = hostname_len;
    int rest_len = end - rest_data; if (rest_len < 4) { LOGMSG("parse out [%.*s], and want more data\n", hostname_len, hostname);  return -1; }
    *is_aaaa = (rest_data[1] == 0x1c) ? 1 : 0;
    if (rest_data[0] != 0x00 || (rest_data[1] != 0x01 && rest_data[1] != 0x1c) || rest_data[2] != 0x00 || rest_data[3] != 0x01) {
        LOGMSG("parse hostname [%.*s], but not query not answer\n", hostname_len, hostname);
        return 0;
    }
    rest_data += 4;
    rest_len -= 4;
    //answers
    while (rest_len > 2) {
        rest_len  -= 2; //skip name
        rest_data += 2; //skip name
        if (rest_len <= 2 + 2 + 4 + 2) { LOGMSG("in one answer part, miss type, class, time to live, data length\n"); return 0; }
        uint8_t type = rest_data[1];
        rest_len  -= 2 + 2 + 4 + 2;               //skip type, class, time to live, data length
        rest_data += 2 + 2 + 4 + 2;               //skip type, class, time to live, data length
        uint8_t next_part_len = rest_data[-1];
        if (type == 5) {
#if 0
            int cname_len = 0;
            uint8_t *p = rest_data;
            for (;;) {
                uint8_t part_len = p[0];
                if (part_len == 0xc0) {
                    p = app_data + p[1]; if (p >= end) { LOGMSG("you try cross data\n"); return -1; }
                } else if (part_len == 0) {
                    break;
                } else {
                    //if (cname_len + 1 + part_len >= sizeof(cnamebuf)) error
                    //memcpy(cname + cname_len, ".", 1);
                    //memcpy(cname + cname_len + 1 , p + 1, part_len);
                    p += 1 + part_len;
                    cname_len += 1 + part_len;
                }
            }
#endif
        } else if (type == 1) {
            //char ipbuf[INET_ADDRSTRLEN] = ""; LOGMSG("ip %s\n", inet_ntop(AF_INET, rest_data, ipbuf, sizeof(ipbuf)));
            uint32_t ipv4; memcpy(&ipv4, rest_data, 4);
            //uint8_t *u8 = (uint8_t *)&ipv4;
            //if (debug_dns > 0) LOGERR("dns answer [%.*s] [%u.%u.%u.%u]\n", hostname_len, hostname, u8[0], u8[1], u8[2], u8[3]);
            //uint32pool_add(remote_nat_ip_pool, be32toh(ipv4), be32toh(ipv4), NULL);
            //struct dns_lib_user_data *a = malloc(sizeof(struct dns_lib_user_data)); if (a == NULL) { LOGFEW("fail to malloc[%m]\n"); }
            //if (a) {
            //    a->ip = ipv4;
            //    a->uptime = time(NULL);
            //    urlmatch_add_url(dns_lib, hostname, a);
            //}
            ip2name_add(ip2name, be32toh(ipv4), hostname, hostname_len, 1, verbose);
            if (callback) callback(hostname, hostname_len, be32toh(ipv4), c);
        }
        rest_len  -= next_part_len;
        rest_data += next_part_len;
    }
    //tuple->dns_context->hostname_len = hostname_len;
    return 0;
}

static inline const char *ip2name_find(struct ip2name *ip2name, uint32_t ip_l)
{
    if (ip2name) {
        struct uint32pool_userdata *userdata = uint32pool_find(ip2name->ip2name_pool, ip_l, NULL, NULL);
        if (userdata) return (char *)userdata->data;
    }
    return "";
}

static inline int ip2name_dump_stat(struct ip2name *ip2name, char *buf, int len)
{
    if (len <= 0) return 0;
    int ret = snprintf(buf, len-1, " ip2name_pool_size %u ip2name_timeout %us rest %"PRIu64"s ip2name_clear_count %u", ip2name->ip2name_pool_size, ip2name->ip2name_timeout, ip2name->ip2name_timeout_last + ip2name->ip2name_timeout - (uint64_t)time(NULL), ip2name->ip2name_clear_count);
    buf[len - 1] = 0;
    if (ret > len -1) ret = len -1;
    return ret;
}

//return 0 ok
//return 1 cached
//return other error
static inline int ip2name_deal_tcp_dns_pack(struct ip2name *ip2name, struct tcp_dns_content *content, uint8_t *app_data, uint32_t app_data_len, int verbose, int (*callback)(char *hostname, int hostname_len, void *c), void *c, void (*callback2)(char *hostname, int hostname_len, uint32_t ip_l, void *c2), void *c2)
//#define ip2name_deal_tcp_dns_pack(a,b,c,d,e,f,g,h) ({ LOGERR("calling ip2name_deal_tcp_dns_pack\n"); ip2name_deal_tcp_dns_pack(a,b,c,d,e,f,g,h); })
{
    if (content->is_not_tcp_dns) return -1;
    int ret = 0;
    while (app_data_len > 2) {
        uint16_t len; memcpy(&len, app_data, 2); len = be16toh(len);
        if (len > app_data_len) {
            if (content->cache == NULL) content->cache_len = 0;
            if (len + content->cache_len > 1600) {
                content->is_not_tcp_dns = 1; tcp_dns_context_fini(content); return -1; }
            if (content->cache == NULL) { content->cache = malloc(1600); content->cache_len = 0; }
            memcpy(content->cache + content->cache_len, app_data, app_data_len);
            content->cache_len += app_data_len;
            return 1;
        }
        app_data += 2;
        app_data_len -= 2;
        char hostname[512] = ""; int hostname_len = 512; int is_aaaa;
        if (ip2name_dns_data_to_host(ip2name, app_data, len, hostname, &hostname_len, &is_aaaa, verbose, callback2, c2, __func__, __LINE__)) {
            content->is_not_tcp_dns = 1;
            tcp_dns_context_fini(content);
            return -1;
        }
        if (callback) ret = callback(hostname, hostname_len, c);
        app_data += len;
        app_data_len -= len;
    }
    return ret;
}


typedef struct hostname_node {
    char *buf;
    int len;
    struct list_head node;
} hostname_node_t;

//static inline int dns_hostname_add(char *hostname, int hostname_len, struct list_head *dns_hostname_list)
#define dns_hostname_add(hostname, hostname_len, dns_hostname_list) ({ \
    hostname_node_t *hostname_node = calloc(1, sizeof(hostname_node_t)); \
    if (hostname_node) { \
        hostname_node->buf = malloc(hostname_len); if (hostname_node->buf == NULL) { free(hostname_node); hostname_node = NULL; } \
        if (hostname_node && hostname_node->buf) { \
            memcpy(hostname_node->buf, hostname, hostname_len); \
            hostname_node->len = hostname_len; \
            list_add_tail(&hostname_node->node, dns_hostname_list); \
        } \
    } \
    0; })

static inline void dns_hostname_list_free(struct list_head *head)
{
    hostname_node_t *_p, *_n; \
        list_for_each_entry_safe(_p, _n, head, node) { \
            list_del(&_p->node); \
                free(_p->buf); \
                free(_p); \
        } \
}

#endif
